package com.e3.core.utils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * XML工具类
 * 包含 XML 操作常用方法
 *
 * @author <a href="mailto:yang_y@sysnet.com.cn">Young Yang</a>
 */
public class XMLUtils {
    private final static DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    private final static TransformerFactory tf = TransformerFactory.newInstance();

    private final static XPathFactory xpf = XPathFactory.newInstance();

    static {
//        tf.setAttribute("indent-number", 4);
        dbf.setValidating(false);
        dbf.setNamespaceAware(true);
    }

	private XMLUtils() {

	}

    /**
     * 获得所有的 child element
     * @param parentNode
     * @return
     */
    public static List<Element> getChildElements(Node parentNode){
        NodeList nodeList = parentNode.getChildNodes();
        List<Element> childElements = new ArrayList<Element>(nodeList.getLength());
        for(int i=0; i<nodeList.getLength(); i++){
            Node childNode = nodeList.item(i);
            
            if (childNode.getNodeType() == Node.ELEMENT_NODE) {
                childElements.add((Element)childNode);
            }
        }
        return childElements;
    }

    /**
	 * get child nodes of the node with the tagName
	 *
	 * @param node
	 * @param tagName
	 */
	public static Node getChildNodeOf(Node node, String tagName) {
		for (Node temp = node.getFirstChild(); temp != null; temp = temp.getNextSibling())
			if (temp.getNodeType() == Node.ELEMENT_NODE && tagName.equals(temp.getNodeName())) {
				return temp;
			}
		return null;
	}

    /**
     * 取得 node 下所有 tagName 的 child node
     * @param node
     * @param tagName
     * @return
     */
    public static List<Node> getChildNodesOf(Node node, String tagName) {
		NodeList nodelist = node.getChildNodes();
		List<Node> nodes = new ArrayList<Node>();
		for (int i = 0; i < nodelist.getLength(); i++) {
			Node temp = nodelist.item(i);
			if (temp.getNodeName().equals(tagName)) {
				nodes.add(temp);
			}
		}
		return nodes;
	}

    /**
     * 取得 node 下 child node 为 tagName 的值
     * @param node
     * @param tagName
     * @return
     */
    public static String getChildNodeValueOf(Node node, String tagName) {
		if (tagName.equals(node.getNodeName())) {
			return getValueOf(node);
		}
		for (Node temp = node.getFirstChild(); temp != null; temp = temp.getNextSibling()) {
			if (temp.getNodeType() == Node.ELEMENT_NODE && tagName.equals(temp.getNodeName())) {
				return getValueOf(temp);
			}
		}
		return null;
	}

    /**
     * 取得 node 的 value
     * @param node
     * @return
     */
    public static String getValueOf(Node node) {
		if (node == null) {
			return null;
		} else if (node instanceof Text) {
			return node.getNodeValue().trim();
		} else if (node instanceof Element) {
			((Element) node).normalize();
			Node temp = node.getFirstChild();
			if (temp != null && (temp instanceof Text))
				return temp.getNodeValue().trim();
			else
				return "";
		} else {
			return node.getNodeValue().trim();
		}
	}

    /**
     * 取得 node 的 一个attribute 的值
     * @param node
     * @param attribute
     * @return
     */
    public static String getAtrributeValueOf(Node node, String attribute) {
		Node _node = node.getAttributes().getNamedItem(attribute);
		return getValueOf(_node);
	}

    /**
     * 取得 element 下 所有某个tag 的 child element 列表
     * @param element
     * @param tag
     * @return
     */
    public static List<Element> getElementsByTagName(Element element, String tag) {
		ArrayList<Element> children = new ArrayList<Element>();
		if (element != null && tag != null) {
			NodeList nodes = element.getElementsByTagName(tag);
			for (int i = 0; i < nodes.getLength(); i++) {
				Node child = nodes.item(i);
				// System.out.println("Name: " + child.getNodeName() + ", Value:
				// " + child.getFirstChild().getNodeValue());
				children.add((Element) child);
			}
		}
		return children;
	}

    /**
     * 取得 element 下 tags 中 child element
     * @param element
     * @param tags
     * @return
     */
    public static List<Element> getElementsByTagNames(Element element, String[] tags) {
		List<Element> children = new ArrayList<Element>();
		if (element != null && tags != null) {
			List<?> tagList = Arrays.asList(tags);
			NodeList nodes = element.getChildNodes();
			for (int i = 0; i < nodes.getLength(); i++) {
				Node child = nodes.item(i);
				if (child.getNodeType() == Node.ELEMENT_NODE && tagList.contains(((Element) child).getTagName())) {
					children.add((Element) child);
					// System.out.println("Name: " + child.getNodeName() + ",
					// Value: " + child.getFirstChild().getNodeValue());
				}
			}
		}
		return children;
	}

	/**
	 * get the xml root document of the xml descriptor
	 *
	 * @param url the xml descriptor url
     * @return XML document
     * @throws Exception if failed to create XML document
	 */
	public static Document loadDocument(URL url) throws IOException, ParserConfigurationException, SAXException {
		InputStream is = null;
		try {
			is = new BufferedInputStream(url.openStream());
			return dbf.newDocumentBuilder().parse(is);
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException e) {

				}
			}
		}
	}

	/**
	 * parse document from xml String
	 *
	 * @param xml
	 * @return
	 * @throws Exception
	 */
	public static Document loadDocument(String xml) throws IOException, ParserConfigurationException, SAXException {
		return dbf.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
	}
	
	/**
	 * parse document from xml String
	 *
	 * @param xml
	 * @return
	 * @throws Exception
	 */
	public static Document loadDocument(File xmlFile) throws IOException, ParserConfigurationException, SAXException {
		return dbf.newDocumentBuilder().parse(xmlFile);
	}
	
	/**
	 * parse document from xml String
	 *
	 * @param xml
	 * @return
	 * @throws Exception
	 */
	public static Document loadDocument(InputStream in) throws Exception {
		try {
			Document doc = dbf.newDocumentBuilder().parse(in);
			in.close();
			return doc;
		} catch (Exception e) {
			in.close();
			throw e;
		}
	}

	/**
	 * new document
	 */
	public static Document newDocument() {
		try {
			return dbf.newDocumentBuilder().newDocument();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 为 parent Node 增加一个 TextElement
	 *
	 * @param parent
	 *            父节点
	 * @param name
	 *            element name
	 * @param value
	 *            element value
	 * @return 增加的 element
	 */
	public static Element addTextElement(Node parent, String name, String value) {
		return addTextElement(parent, name, value, null);
	}

	/**
	 * 为 parent Node 增加一个 TextElement，可定义该Element 的属性。
	 *
	 * @param parent
	 * @param name
	 * @param value
	 * @param attrs
	 */
	public static Element addTextElement(Node parent, String name, String value, Attr[] attrs) {
		Element element = null;
		if (parent instanceof Document) {
			element = ((Document) parent).createElement(name);
		} else {
			element = parent.getOwnerDocument().createElement(name);
		}

		if (attrs != null && attrs.length > 0) {
			for (Attr attr : attrs) {
				element.setAttributeNode(attr);
			}
		}

		if (value != null) {
			element.setTextContent(value);
		}
		parent.appendChild(element);
		return element;
	}

	/**
	 * Add a CDATA element.
	 *
	 * @param parent
	 * @param name
	 * @param data
	 */
	public static Element addCDATAElement(Node parent, String name, String data) {
		return addCDATAElement(parent, name, data, null);
	}

	/**
	 * Add a CDATA element with attributes.
	 *
	 * NOTE: Serializing a XML document via TRAX changes "\r\n" to "\r\r\n" in
	 * a CDATA section. Serializing with the Xalan XMLSerializer works fine.
	 *
	 * @param parent
	 * @param name
	 * @param data
	 * @param attrs
	 */
	public static Element addCDATAElement(Node parent, String name, String data, Attr[] attrs) {
		Element element = null;
		CDATASection cdata = null;
		if (parent instanceof Document) {
			element = ((Document) parent).createElement(name);
			/*
			 * Creates a <code>CDATASection</code> node whose value is the
			 * specified.
			 */
			cdata = ((Document) parent).createCDATASection(data);
		} else {
			element = parent.getOwnerDocument().createElement(name);
			cdata = parent.getOwnerDocument().createCDATASection(data);
		}

		if (attrs != null && attrs.length > 0) {
			for (Attr attr : attrs) {
				element.setAttributeNode(attr);
			}
		}

		element.appendChild(cdata);
		parent.appendChild(element);
		return element;
	}

	/**
	 * 为 parent Node 增加一个空的 Element
	 *
	 * @param parent
	 *            父节点
	 * @param name
	 *            element name
	 * @param attrs
	 *            element 的 attributes
	 * @return 增加的 element
	 */
	public static Element addElement(Node parent, String name, Attr[] attrs) {
		Element element = null;
		if (parent instanceof Document) {
			element = ((Document) parent).createElement(name);
		} else {
			element = parent.getOwnerDocument().createElement(name);
		}
		if (attrs != null && attrs.length > 0) {
			for (Attr attr : attrs) {
				element.setAttributeNode(attr);
			}
		}
		parent.appendChild(element);
		return element;
	}

    public static Element addElement(Element parent, Element childElement){
        Node childNode = parent.getOwnerDocument().importNode(childElement,true);
        parent.appendChild(childNode);
        return parent;
    }

    public static Element addElement(Document parent, Element childElement){
        Node childNode = parent.importNode(childElement,true);
        parent.appendChild(childNode);
        return parent.getDocumentElement();
    }

    public static void removeChild(Node parent, Node child){
        parent.removeChild(child);
    }

    public static void removeChilds(Node parent, String childTagName) {
        List<Node> childNodes = getChildNodesOf(parent, childTagName);

        for(Node childNode : childNodes){
            parent.removeChild(childNode);
        }
    }

    /**
	 * 创建 Attribute
	 *
	 * @param document
	 * @param name
	 * @param value
	 * @return Attr
	 */
	public static Attr createAttribute(Document document, String name, String value) {
		Attr attr = document.createAttribute(name);
		attr.setTextContent(value);
		return attr;
	}

	/**
	 * 将document转成字符串
	 *
	 * @param doc
	 */
	public static String xmlNodeToString(Node doc) {
		if (doc == null) {
			return "";
		}
		try {
			Transformer tran = tf.newTransformer();
			tran.setOutputProperty(OutputKeys.INDENT, "yes");
			StringWriter swriter = new StringWriter();
			Source src = new DOMSource(doc);
			Result res = new StreamResult(swriter);
			tran.transform(src, res);
			return swriter.getBuffer().toString();
		} catch (Exception e) {
			e.printStackTrace();
			return "";
		}
	}

    /**
     * 将 xml document 写到一个 writer 中
     * @param doc
     * @param writer
     */
    public static void xmlNodeToWriter(Document doc, Writer writer){
        if (doc == null || writer == null) {
            return;
        }
        try {
            Transformer tran = tf.newTransformer();
            tran.setOutputProperty(OutputKeys.INDENT, "yes");
            Source src = new DOMSource(doc);
            Result res = new StreamResult(writer);
            tran.transform(src, res);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 执行一个 xpath
     * 
     * @param doc
     * @param xpath
     * @param returnType
     * @return
     * @throws XPathExpressionException
     */
    public static Object evaluateXpath(Document doc, String xpath, QName returnType) throws XPathExpressionException {
        XPath path = xpf.newXPath();
        return path.evaluate(xpath, doc, returnType);
    }

    private static void createXML() {
		Document document = XMLUtils.newDocument();
        Element rspNode = XMLUtils.addElement(document, "response", new Attr[] { XMLUtils.createAttribute(document, "type", "MESSAGE_TYPE") });

		XMLUtils.addTextElement(rspNode, "username", "姓名");
		XMLUtils.addTextElement(rspNode, "password", "User.getPassword()");
		XMLUtils.addTextElement(rspNode, "result", "successful");
		XMLUtils.addTextElement(rspNode, "sessionId", "session_id");
		XMLUtils.addCDATAElement(rspNode, "sessionId", "session_id");

		Element childsElement = XMLUtils.addTextElement(rspNode, "childs", null, null);
		XMLUtils.addTextElement(childsElement, "child", "1");

        Document document2 = XMLUtils.newDocument();
        Element rspNode2 = XMLUtils.addElement(document2, "response", new Attr[] { XMLUtils.createAttribute(document2, "type", "MESSAGE_TYPE") });

        XMLUtils.addElement(rspNode, rspNode2);
        
        System.out.println(XMLUtils.xmlNodeToString(document.getDocumentElement()));
	}

    public static void main(String[] args) throws Exception{
        createXML();
    }

}
